#Written by: Arun Balasubramanian
#This code simulates the logic for local and internode preemptive scheduling for CAN peer to peer technique in remote desktop grids
#This is done by invoking a separate thread for functionality within a node. The communication between nodes is implemented through interthread communication
import time
import thread
import sys

alpha=0
beta=20
class  job:
	def __init__(self, jobid=None, est_time=None, cpu_speed=None, memory=None, disk_size=None, wait_time=None, submit_time=None, p_job=None, cores=None, job_finishtime=None):
		if jobid==None:
			self.jobid = -1
			self.est_time = 0
			self.cpu_speed = 0
			self.memory = 0
			self.disk_size = 0
			self.wait_time = 0
			self.submit_time = 0
			self.p_job = 0
			self.cores = 0
			self.job_finishtime = 0
			self.inqueue=0
			self.running=0
			self.counter=0
			self.chosen = 0
			self.completed =0
			self.total_runtime=0
		else: 	
			self.jobid = jobid
			self.est_time = float(est_time)
			self.cpu_speed = cpu_speed
			self.memory = memory 
			self.disk_size = disk_size
			self.wait_time = float(wait_time)
			self.submit_time = float(submit_time)
			self.p_job = float(p_job)
			self.cores = int(cores)
			self.job_finishtime = float(job_finishtime)
			self.inqueue=0
			self.running=0
			self.counter=0
			self.chosen = 0
			self.completed=0
			self.total_runtime=0
		 	
class node:
	def __init__(self, nodeID, cpu_speed, memory, disk_size, join_time, cores, global_time):
		self.nodeID = nodeID
		self.jobs = []
		self.cpu_speed = cpu_speed
		self.memory = memory
		self.disk_size = disk_size
		self.join_time = join_time
		self.cores = int(cores)
		self.global_time = float(global_time)
		self.curr_jobs = 0
		self.res_cores = int(cores)

	def fetch_next_job(self):
		maxp_job=-99999999999
		top = job()
		for j in self.jobs:
			if j.inqueue==1 and j.completed==0:
				if maxp_job < j.p_job:
					maxp_job = j.p_job
					top = j
		for j in self.jobs:
			if top.jobid == j.jobid:
				j.inqueue=0
		return top
		
#	def append_job(self, jobid):
#		self.jobid.append(jobid)	
	def calculate_priority(self, job):
#		for jid in jobid:
		#	for j in self.jobs:
		#		if j.inqueue == 1 or j.running ==1:		
		#			if self.global_time >= j.submit_time:
		job.p_job = ((alpha*job.wait_time)-(beta*job.est_time))
	#	if job.est_time<10000:
		#print "priority of job "+str(job.p_job)
		return 

	def preempt(self, job1):
		job1.running=0
		job1.inqueue=1
		self.res_cores += job1.cores
		self.curr_jobs -= 1
		job1.chosen=0
		job1.est_time -= (self.global_time - job1.counter)
		job1.total_runtime += (self.global_time - job1.counter)
		job1.counter = self.global_time
		#print "JOB: "+str(job1.jobid)+" preempted. total rtime:"+str(job1.total_runtime)
		if job1.est_time <=0:
			job1.completed=1
			print "total_runtime"+str(job1.total_runtime)
			job1.inqueue=0 	
		return

	def min_currjobs(self):
		min_currjob = job()
		minp=9999999999
		for j in self.jobs:
			if j.running ==1:
	#			print "RUNNING JOB"
				if j.chosen==0:
					if minp > j.p_job:
						min_currjob = j
						minp = j.p_job
						
		for j in self.jobs:
			if min_currjob.jobid == j.jobid:
				j.chosen=1
	#	print "minjob details"+ str(min_currjob.p_job)
		return min_currjob						

	def scheduler(self):
		#the scheduler performs the following scheduling operation everytime it is invoked
		jobs_considered = []
		i=0
		#print "GLOBALTIME: "+str(self.global_time)
	#	j = job()
		for jid in self.jobs:
			jid.chosen = 0
			if jid.running == 1:		#if the job is currently running in queue, check its status and perform necessary actions
				jid.total_runtime += (self.global_time - jid.counter)
				jid.est_time -= (self.global_time - jid.counter)
				jid.counter = self.global_time
		#			print "Est TIME of job:"+j.jobid+"is: "+str(j.est_time)
				if jid.est_time <= 0:	#if the job is completed, mark as complete and invoke the scheduler
					jid.running=0
					jid.inqueue=0
					jid.completed=1
				#	print "ottal runtime: "+str(jid.total_runtime)
					self.curr_jobs -= 1
					self.res_cores += jid.cores	#free up the cores
				else:
					self.calculate_priority(jid)
					
			elif jid.inqueue == 1:		# if the job is waiting in queue, update its waiting time
				jid.wait_time = ((self.global_time - jid.submit_time) - jid.total_runtime)
				self.calculate_priority(jid)
		#			print "Est TIME of job:"+j.jobid+"is: "+str(j.est_time)
			elif ((jid.submit_time < self.global_time) and (jid.inqueue==0 and jid.completed==0)): 	# if the job has arrived, push it in queue and update the wait time
				jid.inqueue = 1
				jid.wait_time = (self.global_time - jid.submit_time)
				self.calculate_priority(jid)
		
		j = self.fetch_next_job()
		minjob = self.min_currjobs()
	#	print "MINJOB.JOBID:"+str(minjob.jobid)
		temp_cores = self.res_cores
		while ((j.p_job > minjob.p_job and minjob.jobid!=-1 and j.jobid!=-1) or (j.cores<=temp_cores and j.jobid!=-1)):
	#			print "INSIDE WHILE ********************************************************************"
			if temp_cores >= j.cores:
				for j1 in jobs_considered:
					self.preempt(j1)	
				j.running = 1
				j.inqueue = 0
				j.counter = self.global_time
				j.chosen=1
				self.res_cores -= j.cores
				self.curr_jobs+=1
			#	j.wait_time = (self.global_time - j.submit_time) - j.total_runtime
				j=self.fetch_next_job()
				temp_cores = self.res_cores
				minjob = self.min_currjobs()
				jobs_considered = []
			else:
				jobs_considered.append(minjob)
				temp_cores+=minjob.cores
				if minjob.cores>= j.cores:
					self.preempt(minjob)
					j.running = 1
					j.inqueue = 0
					j.chosen=1
					j.counter = self.global_time
					for j1 in jobs_considered:
						j1.chosen=0
				#	j.wait_time = (self.global_time - j.submit_time) - j.total_runtime
					self.res_cores -= j.cores
					self.curr_jobs+=1
					j=self.fetch_next_job()
					temp_cores = self.res_cores
					jobs_considered = []
					minjob = self.min_currjobs()
				elif temp_cores >= j.cores:
					for j1 in jobs_considered:
						self.preempt(j1)
					jobs_considered=[]	
					j.running = 1
					j.inqueue = 0
					j.chosen=1
					self.res_cores -= j.cores
				#	print "PRIORITY of job: "+str(j.p_job)
				#	j.wait_time = (self.global_time - j.submit_time) - j.total_runtime
					j.counter = self.global_time
					self.curr_jobs+=1
					j=self.fetch_next_job()
					temp_cores = self.res_cores
					minjob = self.min_currjobs()
				else:					
					minjob = self.min_currjobs()
	#self.backfilling()
		return
		
					

	def jobs_completed(self):
		flag=0
		for j in self.jobs:
			if j.completed==0:
				flag=1
			#	print "jobid:"+str(j.jobid)+"Est time:"+str(j.est_time)+"running status:"+str(j.running)+"queue status"+str(j.inqueue)
		if flag==1:
			return 0
		else:
			return 1
			
			
	def node_operation(self):
		delta = 1000
		tot_jobs=0
		count=0
		while self.jobs_completed()==0:
			count=0
			tot_jobs=0
			self.global_time += 150000
	#		print "GLOBAL TIME: "+ str(self.global_time)
			for j in self.jobs:
				if j.completed==1:
					count+=1
			#	else:
	#				print "jobid:"+str(j.jobid)+"Est time:"+str(j.est_time)+"running status:"+str(j.running)+"queue status"+str(j.inqueue)
				tot_jobs+=1
		#	print "Total jobs:"+str(tot_jobs)+"Finished jobs:"+str(count)
				
			self.scheduler()	
		#print "\nend of while loop"			
		return		
			
		

fp = open("1-IAT1.5S-CC.in", "r")
line = fp.readline()
buff = list()
#string, nodeID, jobid, cpu_speed, memory, disk_size, join_time, cores, global_time = 
buff = line.split(' ')
#print buff[0]
nodeList = []
jobList = []
mark=0
while buff[0] != "exit":
	mark=0
	if buff[0] == "join":
		nodeList.append(node(buff[1],buff[5], buff[6], buff[7], buff[9], buff[8], 0))
	elif buff[0] == "submitJob":
		for n in nodeList:
			if int(n.nodeID) == int(buff[1]):
				if n.cores >= int(buff[9]):
					if len(n.jobs) < 8:
						n.jobs.append(job(buff[2], buff[3], buff[6], buff[7], buff[8], 0, buff[10], 0, buff[9], 0))
						mark=1
						break
		if mark==0:
			for n1 in nodeList:
				if int(n1.nodeID) != int(buff[1]):
					if n1.cores >= int(buff[9]):
						if len(n1.jobs) < 8:
							n1.jobs.append(job(buff[2], buff[3], buff[6], buff[7], buff[8], 0, buff[10], 0, buff[9], 0))
							break
						
		
				
	line = fp.readline()
	buff = line.split(' ')
#	print buff[0]
count=0
for n in nodeList:
	print "NODEID: "+ str(n.nodeID)
	#for j in n.jobs:
#		print "JOBID: "+ str(j.jobid)
	count+=1
print "Count:"+ str(count)
count = 0
for n in nodeList:
	print "\nstarted: "+str(count)
	n.node_operation()
	count +=1

count=0
wait_time=0
sorted_waittimes = list()
highest_waittime = 0
for n in nodeList:
	for j in n.jobs:
		if j.completed==1 and j.est_time<=0:
			wait_time += j.wait_time
			count+=1
			sorted_waittimes.append(j.wait_time)
			if highest_waittime<j.wait_time:
				highest_waittime = j.wait_time
				
print "Average wait time: "+ str(wait_time/count)
sorted_waittimes.sort()
median = sorted_waittimes.pop(2500) + sorted_waittimes.pop(2501)
median = median/2
print "\n Median waittime: "+str(median)
print "total jobs:"+str(count)
print "Highest waittime: "+str(highest_waittime)

#except:
#	print "Error:Unable to start"

 

